﻿using System;
using System.Text;
using System.Reflection;
using Alex.Library.Web.Ajax.Exceptions;

namespace Alex.Library.Web.Ajax
{
    /// <summary>
    /// 生成Ajax客户端脚本的处理基类
    /// </summary>
    abstract class AjaxScriptPageHandler : IAjaxScriptPageHandler
    {
        /// <summary>
        /// 获取模板对象
        /// </summary>
        /// <param name="action">页面中的form的action，用于提交数据</param>
        /// <param name="className">类完全名称</param>
        /// <param name="methods">方法数组</param>
        /// <param name="onLoadScript">在页面加载完成后执行的脚本代码</param>
        /// <returns>模板对象</returns>
        public static IAjaxScriptPageHandler EvalTemplate(string action,
            string className, string methods, string onLoadScript)
        {
            return new AjaxScriptPageTemplate(action, className, methods, onLoadScript);
        }
        
        /// <summary>
        /// 获取生成方法列表HTML代码的对象
        /// </summary>
        /// <param name="methods">方法数组</param>
        /// <returns>生成方法列表HTML代码的对象</returns>
        public static IAjaxScriptPageHandler EvalMethodHtml(MethodInfo[] methods)
        {
            return new MethodHtmlTemplate(methods);
        }

        /// <summary>
        /// 获取生成脚本的对象
        /// </summary>
        /// <param name="className">类名称</param>
        /// <param name="methodNames">方法名称数组</param>
        /// <returns>生成脚本的对象</returns>
        public static IAjaxScriptPageHandler EvalCreateScript(string className, string[] methodNames)
        {
            return new CreateScript(className, methodNames);
        }

        /// <summary>
        /// 获取生成的HTML页面内容
        /// </summary>
        /// <returns></returns>
        public abstract string GetPageContent();

        /// <summary>
        /// 根据类名获取程序集
        /// </summary>
        /// <param name="className">类完全名称</param>
        /// <returns>该类所属的程序集</returns>
        public virtual Assembly GetAssembly(string className)
        {
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (assembly.GetType(className) != null)
                {
                    return assembly;
                }
            }
            return null;
        }
    }

    #region 生成类方法

    /// <summary>
    /// 生成脚本页面的模板类
    /// </summary>
    class AjaxScriptPageTemplate : AjaxScriptPageHandler
    {
        private static string _template;

        static AjaxScriptPageTemplate()
        {
            StringBuilder _temp = new StringBuilder();

            #region Template

            _temp.AppendLine("<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>");
            _temp.AppendLine("<html xmlns='http://www.w3.org/1999/xhtml'>");
            _temp.AppendLine("<head>");
            _temp.AppendLine("<meta http-equiv='Content-Type' content='text/html; charset=gb2312' />");
            _temp.AppendLine("<title>EasyTool Ajax客户端脚本生成器</title>");
            _temp.AppendLine("<style type='text/css'>");
            _temp.AppendLine("* {font-family: '微软雅黑' , Arial, Helvetica, sans-serif;}");
            _temp.AppendLine("body {margin: 0px;font-size: 12px;background: #5c87b2;}");
            _temp.AppendLine(".page {width: 90%;margin-left: auto;margin-right: auto;}");
            _temp.AppendLine("#header {position: relative; margin-bottom: 0px; color: #000; padding: 0;}");
            _temp.AppendLine("#header h1 {font-weight: bold;padding: 5px 0;margin: 0;color: #fff;border: none;line-height: 2em;font-family: Arial, Helvetica, sans-serif;font-size: 32px !important;}");
            _temp.AppendLine("#main {padding: 10px 30px 15px 30px;background-color: #fff;margin-bottom: 30px;_height: 1px;}");
            _temp.AppendLine("#footer {padding: 10px 0;text-align: left;margin: 0;}");
            _temp.AppendLine(".label {float:left;}");
            _temp.AppendLine(".text {border:#5c87b2 1px solid;font-size:12px;float:left;}");
            _temp.AppendLine(".disabled {background:#cccccc;}");
            _temp.AppendLine("#methodsBox {border:#5c87b2 1px solid;width:100%;height:500px;overflow:auto;}");
            _temp.AppendLine("#methodsBox table { border-collapse:collapse; }");
            _temp.AppendLine("#methodsBox table th { background: #5c87b2; border:#B0D1E2 1px solid; color:#ffffff;}");
            _temp.AppendLine("#methodsBox table td { border:#B0D1E2 1px solid;}");
            _temp.AppendLine("#methodsBox table tr.odd {background:#DDEEF7;}");
            _temp.AppendLine("#toolbar {list-style:none;margin:0px;padding:0px;}");
            _temp.AppendLine("#toolbar li {float:left;height:25px;padding:0px 5px 5px 0px;}");
            _temp.AppendLine(".ref {color:#2B91AF;}");
            _temp.AppendLine(".key {color:#0000FF;}");
            _temp.AppendLine("ol { margin:0px; padding:0px; font-size: 12px;}");
            _temp.AppendLine("ol li {margin-left: 20px; padding:5px 0px 0px 0px;}");
            _temp.AppendLine("</style>");
            _temp.AppendLine("<script type='text/javascript'>");
            _temp.AppendLine("function getElementsByClassName(className, tagName) {");
            _temp.AppendLine("var all;");
            _temp.AppendLine("if (tagName != null && tagName != '') {");
            _temp.AppendLine("all = document.getElementsByTagName(tagName);");
            _temp.AppendLine("} else {");
            _temp.AppendLine("all = document.all ? document.all : document.getElementsByTagName('*');");
            _temp.AppendLine("}");
            _temp.AppendLine("var elements = new Array();");
            _temp.AppendLine("for (var e = 0; e < all.length; e++) {");
            _temp.AppendLine("if (all[e].className == className) {");
            _temp.AppendLine("elements[elements.length] = all[e];");            
            _temp.AppendLine("}");
            _temp.AppendLine("}");
            _temp.AppendLine("return elements;");
            _temp.AppendLine("};");
            _temp.AppendLine("function CheckAll(ck) {");
            _temp.AppendLine("var methodList = getElementsByClassName('method', 'input');");            
            _temp.AppendLine("if (methodList != null && methodList.length > 0) {");
            _temp.AppendLine("for (var i = 0; i < methodList.length; i++) {");
            _temp.AppendLine("methodList[i].checked = ck.checked;");
            _temp.AppendLine("}");
            _temp.AppendLine("}");
            _temp.AppendLine("};");
            _temp.AppendLine("function CheckOnGetMethod() {");
            _temp.AppendLine("if (document.getElementById('txtClassName').value == '') {");
            _temp.AppendLine("alert('请输入类完整名称');");
            _temp.AppendLine("return false;");
            _temp.AppendLine("}");
            _temp.AppendLine("document.getElementById('getType').value = '1';");
            _temp.AppendLine("return true;");
            _temp.AppendLine("};");
            _temp.AppendLine("function CheckOnGetScript() {");
            _temp.AppendLine("if (CheckOnGetMethod()) {");
            _temp.AppendLine("var methodList = getElementsByClassName('method', 'input');");
            _temp.AppendLine("if (methodList == null || methodList.length == 0) {");
            _temp.AppendLine("alert('请选择要调用的方法');");
            _temp.AppendLine("return false;");
            _temp.AppendLine("}");
            _temp.AppendLine("document.getElementById('getType').value = '2';");
            _temp.AppendLine("return true;");
            _temp.AppendLine("}");
            _temp.AppendLine("return false;");
            _temp.AppendLine("};");
            _temp.AppendLine("</script>");
            _temp.AppendLine("</head>");
            _temp.AppendLine("");
            _temp.AppendLine("<body onload=\"{3}\">");
            _temp.AppendLine("<form name='form1' method='post' action='{0}' target='_self'>");
            _temp.AppendLine("<div class='page'>");
            _temp.AppendLine("<div id='header'>");
            _temp.AppendLine("<div id='title'>");
            _temp.AppendLine("<h1>");
            _temp.AppendLine("EasyTool Ajax客户端脚本生成器</h1>");
            _temp.AppendLine("</div>");
            _temp.AppendLine("</div>");
            _temp.AppendLine("<div id='main'>");
            _temp.AppendLine("<p>");
            _temp.AppendLine("<label for='txtClassName' class='label'>");
            _temp.AppendLine("Ajax调用的类完整名称：</label>");
            _temp.AppendLine("<input type='text' id='txtClassName' name='txtClassName' class='text' style='width: 500px;' value='{1}'/>");
            _temp.AppendLine("<br />");
            _temp.AppendLine("<ul id='toolbar'>");
            _temp.AppendLine("<li>");
            _temp.AppendLine("<input type='submit' id='btnGetMethod' name='btnGetMethod' value='获取类方法' onclick='return CheckOnGetMethod();'/></li>");
            _temp.AppendLine("<li>");
            _temp.AppendLine("<input type='submit' id='btnGetMethod' name='btnGetScript' value='生成脚本' onclick='return CheckOnGetScript();'/></li>");
            _temp.AppendLine("<li>");
            _temp.AppendLine("<input type='hidden' id='getType' name='getType' value='' /></li>");
            _temp.AppendLine("</ul>");
            _temp.AppendLine("</p>");
            _temp.AppendLine("<br />");
            _temp.AppendLine("<div id='methodsBox'>");
            _temp.AppendLine("{2}");
            _temp.AppendLine("</div>");
            _temp.AppendLine("<div id='footer'>");
            _temp.AppendLine("<ol>");
  			_temp.AppendLine("<li>能够被Ajax调用的方法必须有AjaxMethod特性.</li>");
  			_temp.AppendLine("<li>方法的参数和返回值必须可序列化(object, 如: object GetXXX(),void SetXXX(object obj)).</li>");
  			_temp.AppendLine("<li>暂不支持泛型类和泛型方法, 如: T GetXXX&lt;T&gt;(T obj).</li>");
            _temp.AppendLine("</ol>");
            _temp.AppendLine("</div>");
            _temp.AppendLine("</div>");
            _temp.AppendLine("</div>");
            _temp.AppendLine("</form>");
            _temp.AppendLine("</body>");
            _temp.AppendLine("</html>");

            #endregion

            _template = _temp.ToString();
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="action">页面中的form的action，用于提交数据</param>
        /// <param name="className">类完全名称</param>
        /// <param name="methods">方法数组</param>
        /// <param name="onLoadScript">在页面加载完成后执行的脚本代码</param>        
        public AjaxScriptPageTemplate(string action, string classFullName,
            string methodHtmlContent, string onLoadScript)
        {
            this.Action = action;
            this.ClassFullName = classFullName;
            this.MethodHtmlContent = methodHtmlContent;
            this.OnLoadScript = onLoadScript;
        }

        /// <summary>
        /// 页面中的form的action，用于提交数据
        /// </summary>
        public string Action { get; set; }

        /// <summary>
        /// 类完全限定名
        /// </summary>
        public string ClassFullName { get; set; }

        /// <summary>
        /// 方法列表的HTML内容
        /// </summary>
        public string MethodHtmlContent { get; set; }

        /// <summary>
        /// 在页面加载完成后执行的脚本代码
        /// </summary>
        public string OnLoadScript { get; set; }

        /// <summary>
        /// 获取页面内容
        /// </summary>
        /// <returns></returns>
        public override string GetPageContent()
        {
            return _template.Replace("{0}", this.Action)
                .Replace("{1}", this.ClassFullName)
                .Replace("{2}", this.MethodHtmlContent)
                .Replace("{3}", this.OnLoadScript);
        }
    }

    /// <summary>
    /// 生成方法列表的HTML模板类
    /// </summary>
    class MethodHtmlTemplate : AjaxScriptPageHandler
    {
        private static string _rowTemp;
        private static StringBuilder _template;

        static MethodHtmlTemplate()
        {
            _template = new StringBuilder();
            _template.Append("<table width='100%' cellpadding='0' cellspacing='0' style='border-collapse:collapse;'>");
            _template.Append("<tr>");
            _template.Append("<th width='30' height='25'>");
            _template.Append("<input type='checkbox' id='cbAll' onclick='CheckAll(this);' />");
            _template.Append("</th>");
            _template.Append("<th align='left' style='padding-left:5px;'>");
            _template.Append("Ajax Method");
            _template.Append("</th>");
            _template.Append("</tr>");
            _template.Append("{0}");
            _template.Append("</table>");
            _rowTemp = "<tr class='{3}'>"
                    + "<td align='center' height='23'>"
                    + "<input type='checkbox' id='{0}' name='MethodName' class='method' value='{1}'/></td>"
                    + "<td align='left' style='padding-left:5px;'>{2}</td>"
                    + "</tr>";
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="methods">方法列表</param>
        public MethodHtmlTemplate(MethodInfo[] methods)
        {
            this.Methods = methods;
        }

        /// <summary>
        /// 获取或设置方法列表
        /// </summary>
        public MethodInfo[] Methods { get; set; }

        /// <summary>
        /// 获取方法列表的HTML内容
        /// </summary>
        /// <returns></returns>
        public override string GetPageContent()
        {
            if (this.Methods == null || this.Methods.Length == 0)
            {
                return String.Empty;
            }
            StringBuilder sb = new StringBuilder();
            string rowHtml = _rowTemp.Clone().ToString();
            int i = 0;
            foreach (MethodInfo method in this.Methods)
            {
                if (method.IsAbstract)
                {
                    continue;
                }
                if (method.IsGenericMethod)
                {
                    continue;
                }
                string methodName = GetAjaxScriptMethodName(method);
                string definition = GetMethodDefinition(method);
                definition = definition.Replace("<", "&lt;").Replace(">", "&gt;");
                if (i % 2 == 0)
                {
                    sb.AppendLine(String.Format(rowHtml, "cbm_" + i.ToString(), methodName, definition, "odd"));
                }
                else
                {
                    sb.AppendLine(String.Format(rowHtml, "cbm_" + i.ToString(), methodName, definition, "every"));
                }
                i++;
            }
            string content = _template.ToString();
            string md = sb.ToString();            
            return String.Format(content, md);
        }

        /// <summary>
        /// 获取Ajax方法名称，如果在AjaxMethod特性中设置了Name则取此Name值否则取方法的签名
        /// </summary>
        /// <param name="method">方法信息</param>
        /// <returns>用于Ajax的方法名称</returns>
        private string GetAjaxScriptMethodName(MethodInfo method)
        {
            object[] objs = method.GetCustomAttributes(typeof(AjaxMethodAttribute), false);
            if (objs != null && objs.Length > 0)
            {
                AjaxMethodAttribute attr = objs[0] as AjaxMethodAttribute;
                return !String.IsNullOrEmpty(attr.MethodName) ? attr.MethodName : method.Name;
            }
            return method.Name;
        }

        /// <summary>
        /// 获取方法的定义
        /// </summary>
        /// <param name="method">方法信息</param>
        /// <returns>代码中的方法定义</returns>
        private string GetMethodDefinition(MethodInfo method)
        {
            StringBuilder sb = new StringBuilder();
            //修饰符
            sb.Append("public ");
            //是否是静态方法
            if (method.IsStatic)
            {
                sb.Append("static ");
            }
            //返回类型
            if (method.ReturnType == typeof(void))
            {
                sb.Append("void ");
            }
            else
            {
                string typeName = GetTypeName(method.ReturnType);
                sb.Append(typeName + " ");
            }
            //方法名
            sb.Append(method.Name);
            //是否为泛型
            if (method.IsGenericMethod)
            {
                Type[] genericTypes = method.GetGenericArguments();
                if (genericTypes != null && genericTypes.Length > 0)
                {
                    sb.Append("<");
                    foreach (Type type in genericTypes)
                    {
                        sb.Append(type.Name + ",");
                    }
                    sb.Remove(sb.Length - 1, 1);
                    sb.Append(">");
                }
            }
            //参数
            sb.Append(" (");
            ParameterInfo[] parameters = method.GetParameters();
            if (parameters != null && parameters.Length > 0)
            {
                int i = 0;
                foreach (ParameterInfo parameter in parameters)
                {
                    //is out or not 
                    if (parameter.IsOut)
                    {
                        sb.Append("out ");
                    }
                    else
                    {
                        //is ref or not
                        if (parameter.ParameterType.IsByRef)
                        {
                            sb.Append("ref ");
                        }
                    }
                    //parameter's type & name
                    string name = GetTypeName(parameter.ParameterType) + " " + parameter.Name;
                    //is params or not
                    if (i == parameters.Length - 1)
                    {
                        object[] objs = parameter.GetCustomAttributes(typeof(ParamArrayAttribute), false);
                        if (objs != null && objs.Length > 0)
                        {
                            sb.Append("params ");
                        }
                        sb.Append(name);
                    }
                    else
                    {
                        sb.Append(name + ",");
                    }
                    i++;
                }
            }
            sb.Append(")");

            return sb.ToString();
        }

        /// <summary>
        /// 获取方法定义中的类型签名
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns>在方法定义中的类型签名</returns>
        private string GetTypeName(Type type)
        {
            string paramName = type.Name;
            if (paramName.EndsWith("&"))
            {
                paramName = paramName.Substring(0, paramName.Length - 1);
            }

            if (type.IsGenericType)
            {
                Type[] argTypes = type.GetGenericArguments();
                if (paramName.EndsWith("`" + argTypes.Length))
                {
                    paramName = paramName.Substring(0, paramName.Length - 2);
                }
                paramName += "<";
                foreach (Type t in argTypes)
                {
                    paramName += GetTypeName(t) + ",";
                }
                if (paramName.EndsWith(","))
                {
                    paramName = paramName.Substring(0, paramName.Length - 1);
                }
                paramName += ">";
            }

            return paramName;
        }
    }

    #endregion

    #region 生成脚本

    /// <summary>
    /// 生成脚本的处理类
    /// </summary>
    class CreateScript : AjaxScriptPageHandler
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="className">类完全限定名</param>
        /// <param name="methodNames">作为Ajax的方法名称数组</param>
        public CreateScript(string className, string[] methodNames)
        {
            this.ClassName = className;
            this.MethodNames = methodNames;
            try
            {
                this._reallyMethods = ObjectMethodHandler.GetMethods(className, methodNames);
                if (this._reallyMethods == null || this._reallyMethods.Length == 0)
                {
                    throw new Exception("缺少Ajax方法");
                }
            }
            catch (ClassNotFoundException cex)
            {
                throw cex;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private MethodInfo[] _reallyMethods;

        /// <summary>
        /// 获取或设置类完全限定名
        /// </summary>
        public string ClassName { get; set; }

        /// <summary>
        /// 获取或设置用于Ajax的方法名称数组
        /// </summary>
        public string[] MethodNames { get; set; }

        /// <summary>
        /// 获取生成的脚本内容
        /// </summary>
        /// <returns>脚本内容</returns>
        public override string GetPageContent()
        {
            Assembly assembly = GetAssembly(this.ClassName);
            if (assembly == null)
            {
                throw new AssemblyNotFoundException();
            }
            StringBuilder _scriptBody = new StringBuilder();
            _scriptBody.AppendLine("Utility.CreateClass('" + this.ClassName + "', {");
            _scriptBody.AppendLine("Option: {");
            _scriptBody.AppendLine("RequestType: 'GET',");
            _scriptBody.AppendLine("RequestURL: 'WAjax.aspx',");
            _scriptBody.AppendLine("IsAsync: true,");
            _scriptBody.AppendLine("ReturnType: 'json',");
            _scriptBody.AppendLine("BeforeSendHandler: function () { },");
            _scriptBody.AppendLine("CompleteHandler: function () { },");
            _scriptBody.AppendLine("ErrorHandler: function (XMLHttpRequest, status) { }");
            _scriptBody.AppendLine("},");
            _scriptBody.AppendLine("ResetOption: function () {");
            _scriptBody.AppendLine("this.Option = {");
            _scriptBody.AppendLine("RequestType: 'GET',");
            _scriptBody.AppendLine("RequestURL: 'WAjax.aspx',");
            _scriptBody.AppendLine("IsAsync: true,");
            _scriptBody.AppendLine("ReturnType: 'json',");
            _scriptBody.AppendLine("BeforeSendHandler: function () { },");
            _scriptBody.AppendLine("CompleteHandler: function () { },");
            _scriptBody.AppendLine("ErrorHandler: function (XMLHttpRequest, status) { }");
            _scriptBody.AppendLine("}");
            _scriptBody.AppendLine("},");
            int i = 0;
            foreach (MethodInfo method in _reallyMethods)
            {
                string s = String.Empty;
                string methodName = method.Name;
                _scriptBody.Append(method.Name + ": function (");
                foreach (ParameterInfo parameter in method.GetParameters())
                {
                    _scriptBody.Append("__" + parameter.Name + ",");
                    s += "," + parameter.Name + ": escape($.toJSON(__" + parameter.Name + ")+'::"
                        + parameter.ParameterType.FullName + "')";
                }
                _scriptBody.AppendLine("callback) {");
                _scriptBody.AppendLine("var option = this.Option;");
                _scriptBody.AppendLine("$.ajax({");
                _scriptBody.AppendLine("type: option.RequestType,");
                _scriptBody.AppendLine("async: option.IsAsync,");
                _scriptBody.AppendLine("data: {");
                _scriptBody.AppendLine("__assembly: '" + assembly.FullName + "',");
                _scriptBody.AppendLine("__class: '" + this.ClassName + "',");
                _scriptBody.AppendLine("__method: '" + method.Name + "',");
                _scriptBody.AppendLine("__temp: Math.random(),");
                _scriptBody.AppendLine("__returntype: option.ReturnType");
                if (!String.IsNullOrEmpty(s))
                {
                    _scriptBody.AppendLine(s);
                }
                _scriptBody.AppendLine("},");
                _scriptBody.AppendLine("dataType: option.ReturnType,");
                _scriptBody.AppendLine("url: option.RequestURL,");
                _scriptBody.AppendLine("beforeSend: option.BeforeSendHandler,");
                _scriptBody.AppendLine("success: callback,");
                _scriptBody.AppendLine("complete: option.CompleteHandler,");
                _scriptBody.AppendLine("error: option.ErrorHandler");
                _scriptBody.AppendLine("});");
                if ((i++) < _reallyMethods.Length - 1)
                {
                    _scriptBody.AppendLine("},");
                }
                else
                {
                    _scriptBody.AppendLine("}");
                }
            }
            _scriptBody.AppendLine("});");
            return _scriptBody.ToString();
        }
    }

    #endregion
}
